package com.kinyx.framework.db.sentence.res;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.metamodel.SingularAttribute;

import com.kinyx.framework.Configs;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.db.configs.DBConfigs;
import com.kinyx.framework.db.sentence.defines.SelectResSentence;
import com.kinyx.framework.enums.impl.ErrorCode1;
import com.kinyx.framework.enums.impl.ErrorCodeX;
import com.kinyx.framework.exception.KRuntimeException;
import com.kinyx.framework.utils.KArrayUtils;
import com.kinyx.framework.utils.KListUtils;

import lombok.Getter;

/**
 * 查询语句
 */
@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_")
public class KSELECT extends _Sentence_CRUD implements SelectResSentence {

	KSELECT(final boolean isSQL) { this.isSQL = isSQL; this.withTableAlias = true; }

	@Override
	public boolean isSQL() { return this.isSQL; }

	// ================================================================= SELECT ================================================================= //
	// ============================================ SPECIAL ============================================ //
	private boolean special;

	/** 特殊语句（统计） */
	private String count;

	/**
	 * 统计
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_special")
	public KSELECT count() {
		if (this.special) { throw new KRuntimeException(ErrorCodeX.RepeatCall); }
		this.special = true;
		this.count = "COUNT(1) AS VALUE";
		return this;
	}

	/** 特殊语句（求和） */
	private Column sum;
	/**
	 * 求和
	 * @param column 列
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_special")
	public KSELECT sum(final Column column) {
		if (this.special) { throw new KRuntimeException(ErrorCodeX.RepeatCall); }
		this.special = true;
		this.sum = column;
		return this;
	}
	/**
	 * 求和
	 * @param attr 属性
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_special")
	public KSELECT sum(final SingularAttribute<?, ?> attr) {
		return this.sum(Res.toCol(attr));
	}

	// ============================================ SELECT COLUMNS ============================================ //
	/** 查询列列表 */
	private final List<Column> selectColumns = new ArrayList<>();

	/**
	 * 查询指定列
	 * @param column 列子句
	 * @param columns 列子句
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_select")
	public KSELECT select(final Column column, final Column... columns) {
		if (!this.isSQL) { throw new KRuntimeException(ErrorCode1.CODE_ERROR_803); }
		if (this.special) { throw new KRuntimeException(ErrorCodeX.ConflictCall); }
		this.selectColumns.add(column);
		if (!KArrayUtils.isEmpty(columns)) {
			for (final Column c : columns) {
				this.selectColumns.add(c);
			}
		}
		return this;
	}
	/**
	 * 查询指定列
	 * @param attr 属性
	 * @param attrs 属性
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_select")
	public KSELECT select(final SingularAttribute<?, ?> attr, final SingularAttribute<?, ?>... attrs) {
		return this.select(Res.toCol(attr), Res.toCols(attrs));
	}

	/**
	 * 查询指定列
	 * @param selectColumn 查询列子句
	 * @param selectColumns 查询列子句
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_select")
	public KSELECT select(final SelectColumn selectColumn, final SelectColumn... selectColumns) {
		if (!this.isSQL) { throw new KRuntimeException(ErrorCode1.CODE_ERROR_803); }
		if (this.special) { throw new KRuntimeException(ErrorCodeX.ConflictCall); }
		this.selectColumns.add(selectColumn);
		if (!KArrayUtils.isEmpty(selectColumns)) {
			for (final SelectColumn cr : selectColumns) {
				this.selectColumns.add(cr);
			}
		}
		return this;
	}

	// ============================================ SELECT TABLES ============================================ //
	/** 查询表列表 */
	private final List<Table> selectTables = new ArrayList<>();

	/**
	 * 查询表中所有列
	 * @param tableClass 表映射类型
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_selectAll")
	public KSELECT select(final Class<?> tableClass) {
		if (!this.isSQL) { throw new KRuntimeException(ErrorCode1.CODE_ERROR_803); }
		if (this.special) { throw new KRuntimeException(ErrorCodeX.ConflictCall); }
		this.selectTables.add(Res.of(tableClass));
		return this;
	}

	// ================================================================= DISTINCT ================================================================= //
	/** true：DISTINCT语句；false：正常语句； */
	private Boolean distinct;

	/**
	 * 去重
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_distinct")
	public KSELECT distinct() {
		if (this.distinct != null) { throw new KRuntimeException(ErrorCodeX.RepeatCall); }
		this.distinct = true;
		return this;
	}

	// ================================================================= FROM ================================================================= //
	/** 表子句 */
	private Table mainTable;

	/**
	 * 查询表
	 * @param tableClass 表映射类型
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_from")
	public KSELECT from(final Class<?> tableClass) {
		if (this.mainTable != null) { throw new KRuntimeException(ErrorCodeX.RepeatCall); }
		this.mainTable = Res.of(tableClass);
		return this;
	}

	// ================================================================= 连接表/关联表 ================================================================= //
	/** link or join列表 */
	private List<Table> linkOrJoinTables = new ArrayList<>();

	// 计算表索引值。已经添加过的表，再次添加该表时，索引值+1；与主表一致时，索引值+1；
	private int calcTableIdx(final Table table) {
		if (table.getTableIdx() < 0) { return table.getTableIdx(); }
		for (int i = this.linkOrJoinTables.size() - 1; i >= 0; i--) {// 倒序匹配则可最快匹配到最大值
			final Table linkOrJoinTable = this.linkOrJoinTables.get(i);
			if (table.equals(linkOrJoinTable)) {
				return linkOrJoinTable.getTableIdx() + 1;// 已经添加过的表，再次添加该表时，索引值+1
			}
		}
		return table.equals(this.mainTable) ? 1 : 0;// 与主表一致时，索引值+1
	}

	// ============================================ 连接表 ============================================ //
	/**
	 * 连接表
	 * @param tableClass 表映射类型
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_link")
	public KSELECT link(final Class<?> tableClass) {
		final Table table = Res.of(tableClass);
		this.linkOrJoinTables.add(new LinkTable(table, this.calcTableIdx(table)));
		return this;
	}

	// ============================================ 关联表 ============================================ //
	// 关联表
	private JoinTable appendJoin(final String joinType, final Class<?> tableClass) {
		final Table table = Res.of(tableClass);
		final JoinTable joinTable = new JoinTable(this, table, joinType, this.calcTableIdx(table));
		this.linkOrJoinTables.add(joinTable);
		return joinTable;
	}
	/**
	 * JOIN表
	 * @param tableClass 表映射类型
	 * @return 关联表
	 */
	public JoinTable join(final Class<?> tableClass) { return this.appendJoin("JOIN", tableClass); }
	/**
	 * LEFT JOIN表
	 * @param tableClass 表映射类型
	 * @return 关联表
	 */
	public JoinTable leftJoin(final Class<?> tableClass) { return this.appendJoin("LEFT JOIN", tableClass); }
	/**
	 * RIGHT JOIN表
	 * @param tableClass 表映射类型
	 * @return 关联表
	 */
	public JoinTable rightJoin(final Class<?> tableClass) { return this.appendJoin("RIGHT JOIN", tableClass); }
	/**
	 * FULL JOIN表
	 * @param tableClass 表映射类型
	 * @return 关联表
	 */
	public JoinTable fullJoin(final Class<?> tableClass) { return this.appendJoin("FULL JOIN", tableClass); }
	/**
	 * INNER JOIN表
	 * @param tableClass 表映射类型
	 * @return 关联表
	 */
	public JoinTable innerJoin(final Class<?> tableClass) { return this.appendJoin("INNER JOIN", tableClass); }

	// ================================================================= WHERE ================================================================= //
	/** WHERE子句 */
	private Condition whereCodition;

	/**
	 * 追加WHERE子句
	 * @param condition 条件子句 
	 * @return 本对象
	 */
	public KSELECT where(final Condition condition) {
		if (this.whereCodition != null) { throw new KRuntimeException(ErrorCodeX.RepeatCall); }
		this.whereCodition = condition;
		return this;
	}

	// ================================================================= ORDER BY ================================================================= //
	/** 排序信息对象 */
	private _ORDER_BY orderBy;

	/**
	 * 添加排序信息对象
	 * @param order 排序子句
	 * @param orders 排序子句
	 * @return 本对象
	 */
	@Tester("com.kinyx.framework.db.sentence.res.demo.select.sql.method.SELECT_Tester_SQL_orderBy")
	public KSELECT orderBy(final Order order, final Order... orders) {
		if (this.orderBy == null) {
			this.orderBy = new _ORDER_BY(order, orders);
		} else {
			this.orderBy.addOrders(order, orders);
		}
		return this;
	}

	// ================================================================= 分页数据 ================================================================= //
	/** 分页查询页码 */
	@Getter
	private int pageNumber = 0;

	/**
	 * 设置分页查询页码
	 * @param pageNumber 页码
	 * @return 本对象
	 */
	public KSELECT pageNumber(final int pageNumber) { this.pageNumber = pageNumber; return this; }

	/** 分页/列表查询页面大小 */
	@Getter
	private int pageSize = Configs.default_pageSize;

	/**
	 * 设置分页/列表查询页面大小
	 * @param pageSize 分页/列表查询页面大小
	 * @return 本对象
	 */
	public KSELECT pageSize(final int pageSize) { this.pageSize = pageSize; return this; }

	// ================================================================= 实现方法 ================================================================= //
	@Override
	public String getSentence(final DBConfigs configs, final boolean isSQL, final boolean withTableAlias) {
		final StringBuilder sql = new StringBuilder();

		// SELECT
		if (this.special) {
			sql.append("SELECT").append(" ");
			if (this.count != null) { sql.append(this.count); }
			if (this.sum != null) { sql.append(String.format("SUM(%s) AS VALUE", this.sum.getSentence(configs, isSQL, true))); }
			if (this.mainTable != null) {
				sql.append(" ");
			}
		} else {
			if (isSQL) {
				sql.append("SELECT");
				// DISTINCT
				if (Boolean.TRUE.equals(this.distinct)) { sql.append(" ").append("DISTINCT"); }
				// select指定表全字段
				final boolean hasSelectTables = !this.selectTables.isEmpty();
				if (hasSelectTables) {
					sql.append(" ").append(KListUtils.join(this.selectTables, ", ", (table, index) -> { return table.getTableAlias(configs) + ".*"; }));

				}
				// select指定字段
				final boolean hasSelectColumns = !this.selectColumns.isEmpty();
				if (hasSelectColumns) {
					// 已有指定表全字段，需要续接逗号。
					if (hasSelectTables) { sql.append(","); }
					sql.append(" ").append(KListUtils.join(this.selectColumns, ", ", (column, index) -> { return column.getSentence(configs, isSQL, withTableAlias); }));

				}
				// 没有指定select，则拼接主表全字段。
				if (!hasSelectTables && !hasSelectColumns) {
					if (this.mainTable != null) {
						sql.append(" ").append(this.mainTable.getTableAlias(configs)).append(".*");
					}
				}
				if (this.mainTable != null) {
					sql.append(" ");
				}
			} else {
				// DISTINCT
				if (Boolean.TRUE.equals(this.distinct)) {
					sql.append("SELECT").append(" ").append("DISTINCT").append(" ").append(this.mainTable.getTableAlias(configs)).append(" ");
				}
			}
		}

		// FROM
		if (this.mainTable != null) {
			sql.append("FROM").append(" ").append(this.mainTable.getSentence(configs, isSQL, withTableAlias));
		}

		// 连接表与关联表
		this.linkOrJoinTables.forEach(table -> {
			sql.append(table.getSentence(configs, isSQL, withTableAlias));
		});

		// WHERE
		if (this.whereCodition != null) {
			sql.append(" ").append("WHERE").append(" ").append(this.whereCodition.getSentence(configs, isSQL, withTableAlias));
		}

		// ORDER BY
		if (this.orderBy != null) {
			sql.append(" ").append(this.orderBy.getSentence(configs, isSQL, withTableAlias));
		}

		return sql.toString();
	}

	@Override
	public List<Object> getParams() {
		final List<Object> params = new ArrayList<>();
		// joinTables params
		this.linkOrJoinTables.forEach(table -> {
			if (table instanceof JoinTable) {
				params.addAll(((JoinTable) table).getParams());
			}
		});
		// WHERE params
		if (this.whereCodition != null) {
			params.addAll(this.whereCodition.getParams());
		}
		return params;
	}

}
